package com.wulian.common.aop;

import com.wulian.common.constants.CommonConstants;
import com.wulian.common.context.BaseContextHandler;
import com.wulian.common.dto.DtuPeopleBingInfoDto;
import com.wulian.common.dto.UserInfo;
import com.wulian.common.entity.CarSysUser;
import com.wulian.common.entity.SysUser;
import com.wulian.common.suport.RibbonFilterContext;
import com.wulian.common.suport.RibbonFilterContextHolder;
import com.wulian.common.util.Const;
import com.wulian.common.util.DateUtil;
import com.wulian.common.util.JSONUtil;
import com.wulian.common.util.log.Log;

import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

/**
 * 
 * @ClassName: ServiceMethodAop
 * @Description: TODO(拦截方法耗时)
 * @author 马志豪
 * @date 2019年9月10日 下午3:21:11
 *
 */
@Component
@Aspect
@Slf4j
public class ServiceMethodAop {

	@Autowired
	private StringRedisTemplate redisTemplate;

	private final ThreadLocal<String> methodDescribe = new ThreadLocal<>();

	/**
	 * 定义切点 execution (* com.sunshop.*.service.impl.*.*(..))
	 * within(@org.springframework.stereotype.Service *) 拦截@Service
	 */
	private static final String SERVICE_POINT = "within(@org.springframework.stereotype.Service *)";
	private static final String CONTROLLER_POINT = "within(@org.springframework.web.bind.annotation.RestController *)";

	/**
	 * 统计service方法执行耗时
	 *
	 * @param joinPoint
	 * @return
	 */
	@Around(SERVICE_POINT)
	public Object serviceAround(ProceedingJoinPoint joinPoint) throws Throwable {
		log.debug("ServiceMethodAop around is begin");

		long beginTime = DateUtil.getCurTimeMillis();

		Object obj = joinPoint.proceed(joinPoint.getArgs());
		// 总耗时

		long endTime = DateUtil.getCurTimeMillis();
		long totalTime = endTime - beginTime;

		// 获取执行的方法名
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		String methodName = signature.getDeclaringTypeName() + Const.SEPRATOR_PACKAGE + signature.getName();
		// 耗时信息
		ServiceMethodTimeBean serviceMethodTimeBean = getServiceMethodTimeBean(beginTime, endTime, totalTime,
				methodName);

		if("com.wulian.car.manager.controller.AioController.aioPoll".equals(methodName) == false){
			if (totalTime > Const.WARN_SERVICE_TIME_OUT) {
				log.warn(Log.KEYWORD_SERVICE_TIME, JSONUtil.toJSON(serviceMethodTimeBean));
			} else {
				log.info(Log.KEYWORD_SERVICE_TIME, JSONUtil.toJSON(serviceMethodTimeBean));
			}
		}
		
		return obj;
	}

	/**
	 * 统计controller方法执行耗时
	 *
	 * @param joinPoint
	 * @return
	 */
	@Around(CONTROLLER_POINT)
	public Object controllerAround(ProceedingJoinPoint joinPoint) throws Throwable {
		String json = JSONUtil.toJSON(BaseContextHandler.getUserInfo());
		if ((StringUtils.isBlank(json)) || ("null".equals(json))) {
			RibbonFilterContext context = RibbonFilterContextHolder.getCurrentContext();
			json = context.get("userInfo");
		}
		log.debug("ControllerMethodAop around is begin");
		long beginTime = DateUtil.getCurTimeMillis();
		Object obj = joinPoint.proceed(joinPoint.getArgs());
		// 获取执行的方法名
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		String methodName = signature.getDeclaringTypeName() + Const.SEPRATOR_PACKAGE + signature.getName();
		boolean annotationPresent = signature.getMethod().isAnnotationPresent(ApiOperation.class);
		if (annotationPresent) {
			ApiOperation annotation = signature.getMethod().getAnnotation(ApiOperation.class);
			String notes = annotation.notes();
			methodName += notes;
		}

		long endTime = DateUtil.getCurTimeMillis();
		// 总耗时
		long totalTime = endTime - beginTime;
		ServiceMethodTimeBean serviceMethodTimeBean = getServiceMethodTimeBean(beginTime, endTime, totalTime,
				methodName);

		if("com.wulian.car.manager.controller.AioController.aioPoll".equals(methodName) == false){
		if (totalTime > Const.WARN_SERVICE_TIME_OUT) {
			log.warn(Log.KEYWORD_SERVICE_TIME, JSONUtil.toJSON(serviceMethodTimeBean));
		} else {
			log.info(Log.KEYWORD_SERVICE_TIME, JSONUtil.toJSON(serviceMethodTimeBean));
		}
		}
		return obj;
	}

	private ServiceMethodTimeBean getServiceMethodTimeBean(long beginTime, long endTime, long totalTime,
			String methodName) {
		// 耗时信息
		ServiceMethodTimeBean serviceMethodTimeBean = new ServiceMethodTimeBean();
		serviceMethodTimeBean.setMethodName(methodName);
		serviceMethodTimeBean.setReceiveTime(beginTime);
		serviceMethodTimeBean.setCompleteTime(endTime);
		serviceMethodTimeBean.setTotalTime(totalTime);
		return serviceMethodTimeBean;
	}

	// 切面拦截在调用相应方法前执行
	@Before(CONTROLLER_POINT)
	public void doBefore(JoinPoint joinPoint) throws Throwable {
		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		recordMethodLog(joinPoint, attributes.getRequest());
	}

	// 切面拦截在调用相应方法返回结果之后执行
	@AfterReturning(returning = "ret", pointcut = CONTROLLER_POINT)
	public void doAfterReturning(JoinPoint joinPoint,Object ret) throws Throwable {
		MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
		String methodName = methodSignature.getDeclaringTypeName() + Const.SEPRATOR_PACKAGE + methodSignature.getName();
		if("com.wulian.car.manager.controller.AioController.aioPoll".equals(methodName) == false){
		log.info(methodDescribe.get() + "返回结果 : {}", JSONUtil.toJSON(ret));
		}
		methodDescribe.remove();
	}

	// 入参组装
	private void recordMethodLog(JoinPoint joinPoint, HttpServletRequest request) {
		// 从header中获取用户信息
		String userName = getUserName(request);
		MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
		Method method = methodSignature.getMethod();
		ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
		String value = "";
		if (null != apiOperation) {
			value = apiOperation.value();
			if (StringUtils.isNotEmpty(value)) {
				methodDescribe.set(value);
			}
		}
		String methodName = methodSignature.getDeclaringTypeName() + Const.SEPRATOR_PACKAGE + methodSignature.getName();
		if("com.wulian.car.manager.controller.AioController.aioPoll".equals(methodName) == false){
		// 字符串拼接
		log.info(userName + "操作" + value + "入参：{} ", parseArgs(joinPoint));
		}
	}

	// 入参组装
	private String parseArgs(JoinPoint joinPoint) {
		StringBuffer sb = new StringBuffer();
		Object[] args = joinPoint.getArgs();
		if (args != null) {
			for (Object arg : args) {
				if (arg instanceof BindingResult || arg instanceof HttpServletRequest
						|| arg instanceof HttpServletResponse || arg instanceof MultipartFile
						|| arg instanceof MultipartFile[]) {
					continue;
				}
				sb.append(" ");
				sb.append(JSONUtil.toJSON(arg));
			}
		}
		return sb.toString();
	}

	// 从header获取用户信息
	public String getUserName(HttpServletRequest request) {
		// 获取认证名称
		String token = request.getHeader(CommonConstants.USER_TOKEN);
		String userInfoJson = null;
		if (StringUtils.isNoneBlank(token)) {
			// 用户请求时会在头部 header 传给我之前存储的token, 我用来验证
			// 判断token是否存在
			if (redisTemplate.hasKey(CommonConstants.USER_TOKEN_PREFIEX + token)) {
				userInfoJson = redisTemplate.opsForValue().get(CommonConstants.USER_TOKEN_PREFIEX + token);
			}
		}

		// 判断从redis中获取的用户信息不为空
		if (StringUtils.isNotBlank(userInfoJson)) {
			UserInfo user = JSONUtil.fromJSON(userInfoJson, UserInfo.class);
			SysUser sysUser = user.getSysUser();
			if (null != sysUser) {

				return user.getSysUser().getUsername();

			}
			DtuPeopleBingInfoDto dtuPeopleBingInfoDto = user.getDtuPeopleBingInfoDto();
			if (null != dtuPeopleBingInfoDto) {

				return dtuPeopleBingInfoDto.getName();

			}
			/*CarSysUser carSysUser = user.getCarSysUser();
			if (null != carSysUser) {

				return carSysUser.getName();

			}*/
		}
		return "";
	}

}
